package com.stock.security.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.stock.constant.StockConstant;
import com.stock.domain.vo.request.LoginRequest;
import com.stock.domain.vo.response.LoginRespVoExt;
import com.stock.domain.vo.response.Response;
import com.stock.domain.vo.response.ResponseCode;
import com.stock.security.user.LoginUserDetail;
import com.stock.security.utils.JwtTokenUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;

// TODO 认证过滤器
//  核心作用：认证用户信息，并颁发jwt票据
public class JwtLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    private RedisTemplate redisTemplate;

    //通过构造器传入自定义的登录地址
    public JwtLoginAuthenticationFilter(String loginUrl) {
        super(loginUrl);
    }

    // 通过set方法注入redisTemplate对象
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /*
    TODO 处理用户认证的方法
      我们约定请求方式必须是post方式，且请求的数据时json格式
          约定请求是账户key：username  密码：password
     返回值是Authentication对象，该对象封装了用户信息（用户名和明文密码）
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException {
        // 判断是否是post请求，并且是json格式的请求
        if (!request.getMethod().equalsIgnoreCase("POST") || !(MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(request.getContentType()) || MediaType.APPLICATION_JSON_UTF8_VALUE.equalsIgnoreCase(request.getContentType()))) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        //获取post请求ajax传入的数据流
        LoginRequest reqVo = new ObjectMapper().readValue(request.getInputStream(), LoginRequest.class);

        //设置响应给前端的格式和编码
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");

        //校验验证码是否正确，根据传入的rkey从redis中获取验证码
        String rCheckCode = (String) redisTemplate.opsForValue().get(StockConstant.CHECK_PREFIX + reqVo.getSessionId());

        //判断获取的验证码是否存在，以及是否与输入的验证码相同（忽略大小写）
        if (rCheckCode == null || !rCheckCode.equalsIgnoreCase(reqVo.getCode())) {
            //封装验证码输入错误的信息
            Response<Object> error = Response.error(ResponseCode.CHECK_CODE_ERROR.getMessage());
            String jsonData = new ObjectMapper().writeValueAsString(error);
            //响应给前端
            response.getWriter().write(jsonData);
            return null;
        }

        //获取用户名和密码
        String username = reqVo.getUsername();
        username = (username != null) ? username : "";  //判断用户名是否为空
        username = username.trim(); //去除用户名前后的空格
        String password = reqVo.getPassword();
        password = (password != null) ? password : "";

        //封装用户名和密码
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

        //返回给认证管理器
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    // TODO 用户认证成功后回调的方法，认证成功后，响应前端token信息
    //  参数 chain 过滤器链，authResult 认证成功返回的对象
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        // 设置响应给前端的格式和编码
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");

        // 获取认证成功后的用户详情信息
        LoginUserDetail principal = (LoginUserDetail) authResult.getPrincipal();
        // 获取用户名
        String username = principal.getUsername();
        // 获取用户权限集合
        Collection<GrantedAuthority> authorities = principal.getAuthorities();

        // 生成票据（token）
        String tokenStr = JwtTokenUtil.createToken(username, authorities.toString());

        // 构建响应的实体对象
        LoginRespVoExt respVo = new LoginRespVoExt();
        // 拷贝用户信息 将认证成功后的用户详情信息 拷贝到 响应对象中
        BeanUtils.copyProperties(principal, respVo);
        // 设置票据
        respVo.setAccessToken(tokenStr);
        // 封装响应对象
        Response<LoginRespVoExt> ok = Response.ok(respVo);
        // 将对象转为json字符串，响应给前端
        response.getWriter().write(new ObjectMapper().writeValueAsString(ok));
    }


    // TODO 认证失败后，回调的方法
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        // 设置响应给前端的格式和编码
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");

        // 封装错误信息
        Response<Object> error = Response.error(ResponseCode.ERROR);
        // 响应给前端
        response.getWriter().write(new ObjectMapper().writeValueAsString(error));
    }
}
